package com.xinghen.common.core.interceptor;

import com.google.common.base.Charsets;
import com.xinghen.common.base.GlobalConstant;
import com.xinghen.common.base.model.dto.LoginUser;
import com.xinghen.common.core.annotation.AnonAccess;
import com.xinghen.common.util.ThreadContextHolder;
import com.xinghen.common.util.RedisKeyUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Objects;

/**
 * @author xinghen
 * @ClassName: TokenInterceptor
 * @Package: com.xinghen.common.core.interceptor
 * @Description jwt 拦截器
 * @create 2018-04-16 15:18
 */
@Slf4j
public class TokenInterceptor implements HandlerInterceptor {

    private static final String OPTIONS = "OPTIONS";
    private static final String AUTH_PATH1 = "/auth";
    private static final String AUTH_PATH2 = "/oauth";
    private static final String AUTH_PATH3 = "/error";
    private static final String AUTH_PATH4 = "/api";

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Value("${xinghen.cloud.oauth2.jwtSigningKey}")
    private String jwtSigningKey;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String uri = request.getRequestURI();
        log.info("<== preHandle - 权限拦截器.  url={}", uri);
        if (uri.contains(AUTH_PATH1) || uri.contains(AUTH_PATH2) || uri.contains(AUTH_PATH3) ||
                uri.contains(AUTH_PATH4)) {
            log.info("<== preHandle - 配置URL不走认证.  url={}", uri);
            return true;
        }
        if (OPTIONS.equalsIgnoreCase(request.getMethod())) {
            log.info("<== preHandle - 调试模式不走认证.  url={}", uri);
            return true;
        }
        if (isHaveAccess(handler)) {
            log.info("<== preHandle - 不需要认证注解不走认证.  token={}");
            return true;
        }
        String token = StringUtils.substringAfter(request.getHeader(HttpHeaders.AUTHORIZATION), "Bearer ");
        log.info("<== preHandle - 权限拦截器.  token={}", token);
        LoginUser loginUser = (LoginUser) redisTemplate.opsForValue().get(RedisKeyUtil.getAccessTokenKey(token));
        if (Objects.isNull(loginUser)) {
            log.error("获取用户信息失败, 不允许操作");
            return false;
        }
        log.info("<== preHandle - 权限拦截器.  loginUser={}", loginUser);
        ThreadContextHolder.put(GlobalConstant.Sys.CURRENT_LOGIN_USER, loginUser);
        log.info("<== preHandle - 权限拦截器.  url={}, loginUser={}", uri, loginUser);
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
                                Object handler, Exception ex) throws Exception {
        if (Objects.nonNull(ex)) {
            log.error("<== afterCompletion - 解析token失败. ex={}", ex.getMessage(), ex);
            handleException(response);
        }
    }

    /**
     * 处理异常信息
     *
     * @param response
     */
    private void handleException(HttpServletResponse response) throws IOException {
        response.resetBuffer();
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setHeader("Access-Control-Allow-Credentials", "true");
        response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
        response.setCharacterEncoding(Charsets.UTF_8.name());
        response.getWriter().write("{\"code\":100009 ,\"message\" :\"解析token失败\"}");
        response.flushBuffer();
    }

    /**
     * 是否可以匿名访问
     *
     * @param handler
     * @return
     */
    private boolean isHaveAccess(Object handler) {
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        Method method = handlerMethod.getMethod();
        AnonAccess responseBody = AnnotationUtils.findAnnotation(method, AnonAccess.class);
        return responseBody != null;
    }
}
